package com.vhall.component.service.gift.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.vhall.component.dao.gift.GiftMapper;
import com.vhall.component.dao.gift.GiftMappingMapper;
import com.vhall.component.dao.gift.GiftRecordMapper;
import com.vhall.component.entity.export.ExportEntity;
import com.vhall.component.entity.gift.dto.*;
import com.vhall.component.entity.gift.entity.Gift;
import com.vhall.component.entity.gift.entity.GiftMapping;
import com.vhall.component.entity.gift.entity.GiftRecord;
import com.vhall.component.entity.gift.vo.*;
import com.vhall.component.entity.page.vo.PageVO;
import com.vhall.component.entity.room.dto.RoomsEntityDTO;
import com.vhall.component.framework.common.exception.BusinessException;
import com.vhall.component.framework.common.utils.StringUtils;
import com.vhall.component.plugin.common.enums.DeleteStatusEnum;
import com.vhall.component.plugin.common.exception.BizErrorCode;
import com.vhall.component.plugin.common.support.Token;
import com.vhall.component.plugin.common.support.TokenThreadLocal;
import com.vhall.component.plugin.common.utils.Constants;
import com.vhall.component.plugin.common.utils.RedisLocker;
import com.vhall.component.service.export.ExportService;
import com.vhall.component.service.gift.ClsGiftService;
import com.vhall.component.service.room.RoomInnerService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * ClsGiftServiceImpl-
 *
 * @author xing.wang
 * @date 2022/10/10
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class ClsGiftServiceImpl extends ServiceImpl<GiftMappingMapper, GiftMapping> implements ClsGiftService {

    @Value("${paas.apps.lite.appId}")
    public String appId;
    @Value("${paas.apps.lite.appSecret}")
    public String appSecret;
    /**
     * 房间关联表情数量上限
     */
    @Value("${project.room.giftMappingLimitNum:20}")
    public Integer giftMappingLimitNum;


    private static final String BIND_GIFT_ROOM_LOCK_KEY = "bind:gift:room:";
    private static final DateTimeFormatter DATE_FORMATTER_YMD = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    /**
     * 房间赠送表情信息下载回调接口信息
     */
    private static final String GIFT_RECORD_SERVICE_GIFT_RECORD_EXPORT = "giftRecordService:giftRecordExport";
    /**
     * 房间关联表情下载标识
     */
    private static final String GIFT_RECORD_SERVICE_EXPORT = "giftDetailList";
    /**
     * 导出文件的列头信息,调整后需要对应调整task任务中的执行内容
     */
    private static final String[] GIFT_RECORD_EXPORT_HEADER = {"赠送者ID", "昵称", "表情ID", "表情名称", "数量", "表情单价", "总价值", "赠送时间"};


    private final GiftMapper giftMapper;
    private final GiftMappingMapper giftMappingMapper;
    private final GiftRecordMapper giftRecordMapper;
    private final RoomInnerService roomInnerService;
    private final ExportService exportService;

    @Override
    public PageVO<GiftListDataDTO> unboundRoomList(GiftUnbindRoomListVO vo) {
        LambdaQueryWrapper<Gift> where = Wrappers.lambdaQuery();
        where.eq(Gift::getDeleted, DeleteStatusEnum.NO_DELETE.getStatus())
                .eq(Gift::getAppId, appId)
                .like(StringUtils.isNotEmpty(vo.getKeyword()), Gift::getName, vo.getKeyword())
                .apply("not exists(select gm.gift_id from gift_mapping gm where gm.room_id={0} and gm.gift_id=gift.id)", vo.getRoomId())
                .orderByDesc(Gift::getId);
        Integer pageIndex = vo.getPage();
        Integer pageSize = vo.getPageSize();
        Page<Gift> page = new Page<>(pageIndex, pageSize);
        Page<Gift> quickRepliesEntityPage = giftMapper.selectPage(page, where);
        List<GiftListDataDTO> list = new ArrayList<>();
        if (Objects.nonNull(quickRepliesEntityPage) && quickRepliesEntityPage.getTotal() > 0) {
            list = quickRepliesEntityPage.getRecords().stream().map(entity -> {
                GiftListDataDTO dto = new GiftListDataDTO();
                BeanUtil.copyProperties(entity, dto, false);
                return dto;
            }).collect(Collectors.toList());
        }
        return new PageVO<>(pageIndex, pageSize, (int) page.getTotal(), list);
    }

    @Override
    public GiftPageDTO<GiftBindListDTO> bindRoomList(GiftBindRoomListVO vo) {
        Integer pageIndex = vo.getPage();
        Integer pageSize = vo.getPageSize();
        LambdaQueryWrapper<GiftMapping> where = Wrappers.lambdaQuery();
        String roomId = vo.getRoomId();
        RoomsEntityDTO roomInfo = roomInnerService.getRoomInfo(roomId);
        where.eq(GiftMapping::getRoomId, roomId)
                .eq(GiftMapping::getAppId, appId)
                .apply(StringUtils.isNotEmpty(vo.getKeyword()),
                        " exists ( select gift.id from gift " +
                                " where gift.id=gift_mapping.gift_id and gift.name like concat('%',{0},'%') and gift.app_id={1} )", vo.getKeyword(), appId);
        int total = giftMappingMapper.selectCount(where);
        int startIndex = (pageIndex - 1) * pageSize;
        List<GiftBindListDTO> list = giftMappingMapper.selectBindRoomGiftByRoomIdLikeGiftNamePageList(roomId, appId, vo.getKeyword(), startIndex, pageSize);

        GiftPageDTO<GiftBindListDTO> result = new GiftPageDTO<>(pageIndex, pageSize, total, list);
        result.setGiftFrequency(roomInfo.getGiftFrequency());
        return result;
    }


    @Override
    public Boolean bindRoomChangeFrequency(GiftBindRoomVO vo) {
        if (Objects.nonNull(vo.getChangeList()) && 1 == vo.getChangeList()) {
//            使用锁防止出现多线程更新序号,或解除关联导致关联礼品序号错乱
            String lockKey = BIND_GIFT_ROOM_LOCK_KEY + ":" + vo.getRoomId();
            RedisLocker.lock(lockKey, vo.getRoomId());
            try {
                return bindGiftRoom(vo);
            } finally {
                RedisLocker.unlock(lockKey, vo.getRoomId());
            }
        } else {
            return changeRoomGiftFrequency(vo.getRoomId(), Optional.ofNullable(vo).map(GiftBindRoomVO::getGiftFrequency).orElse(0));
        }
    }

    /**
     * 进行绑定表情到房间
     *
     * @param vo 参数
     * @return 是否绑定成功
     */
    private Boolean bindGiftRoom(GiftBindRoomVO vo) {
        String roomId = vo.getRoomId();
        List<Integer> giftIds = vo.getGiftIds();
        if (Objects.isNull(giftIds) || giftIds.isEmpty()) {
            throw new BusinessException(BizErrorCode.COMP_GIFT_MAPPING_GIFT_ID_NOT_EMPTY);
        }
//        去重复,去掉0,null
        giftIds = giftIds.stream().distinct().filter(giftId -> Objects.nonNull(giftId) && giftId != 0).collect(Collectors.toList());
        if (giftIds.isEmpty()) {
            throw new BusinessException(BizErrorCode.COMP_GIFT_MAPPING_GIFT_ID_NOT_EMPTY);
        }
        checkBindGiftRoom(roomId, giftIds);
        LocalDateTime now = LocalDateTime.now();
//        取得当前关联表情数量
        AtomicInteger sortData = new AtomicInteger(getGiftRoomMappingCount(roomId));
//        添加后表情不能超过限制关联表情数量
        if (giftMappingLimitNum <= (sortData.get() + giftIds.size())) {
            throw new BusinessException(BizErrorCode.COMP_GIFT_MAPPING_COUNT_EXCEED_LIMIT);
        }
        List<GiftMapping> list = giftIds.stream().map(giftId -> {
//            排序为当前关联表情数量+1
            sortData.getAndAdd(1);
            GiftMapping entity = new GiftMapping();
            entity.setCreatedAt(now);
            entity.setUpdatedAt(now);
            entity.setGiftId(giftId);
            entity.setRoomId(roomId);
            entity.setAppId(appId);
            entity.setSort(sortData.get());
            return entity;
        }).collect(Collectors.toList());
        return saveBatch(list);
    }

    /**
     * 进行房间和表情的关联校验
     *
     * @param roomId  房间id
     * @param giftIds 表情id组
     */
    private void checkBindGiftRoom(String roomId, List<Integer> giftIds) {
        LambdaQueryWrapper<GiftMapping> where = Wrappers.lambdaQuery();
        where.eq(GiftMapping::getRoomId, roomId)
                .in(GiftMapping::getGiftId, giftIds);
        int count = count(where);
        if (count >= 1) {
            throw new BusinessException(BizErrorCode.COMP_GIFT_MAPPING_GIFT_BIND_REPEAT);
        }

    }

    /**
     * 获取当前房间下所关联的表情数量
     *
     * @param roomId 房间id
     * @return 获取新的排序号
     */
    private Integer getGiftRoomMappingCount(String roomId) {
        LambdaQueryWrapper<GiftMapping> where = Wrappers.lambdaQuery();
        where.eq(GiftMapping::getRoomId, roomId);
        return count(where);
    }

    /**
     * 更新房间的表情使用频率
     *
     * @param roomId        房间id
     * @param giftFrequency 表情使用频率
     * @return 是否更新成功
     */
    private Boolean changeRoomGiftFrequency(String roomId, Integer giftFrequency) {
        return roomInnerService.setGiftFrequency(roomId, giftFrequency);
    }


    @Override
    public Boolean unbindRoom(GiftUnbindRoomVO vo) {
        GiftMapping giftMapping = Optional.ofNullable(giftMappingMapper.selectById(vo.getBindId()))
                .orElseThrow(() -> new BusinessException(BizErrorCode.COMP_GIFT_MAPPING_EMPTY));
        Integer sort = giftMapping.getSort();
        int i = giftMappingMapper.deleteById(vo.getBindId());
        if (i >= 1) {
            updateRegionGiftMappingSort(vo.getRoomId(), sort, giftMappingLimitNum, ChangeSortRuleEnum.DECREMENT);
        } else {
            return false;
        }
        return true;
    }

    /**
     * 更新区间内关联表情的排序序号
     *
     * @param roomId     房间id
     * @param startIndex 排序开始位置
     * @param endIndex   排序结束位置-为-1时表示重算到最后
     * @param changeRule 排序变更规则[1,-1],1:表示区间内关联表情排序+1,-1:标识区间内关联表情排序-1
     */
    private void updateRegionGiftMappingSort(String roomId, Integer startIndex, Integer endIndex, ChangeSortRuleEnum changeRule) {
        if (startIndex <= endIndex) {
            giftMappingMapper.updateRegionGiftMappingSort(roomId, startIndex, endIndex, changeRule.changeSort);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean setSort(GiftChangeSortRoomVO vo) {
        Integer newSort = vo.getSort();
//        使用锁防止出现多线程更新序号,或解除关联导致关联礼品序号错乱
        String lockKey = BIND_GIFT_ROOM_LOCK_KEY + ":" + vo.getRoomId();
        RedisLocker.lock(lockKey, vo.getRoomId());
        try {
            Integer giftRoomMappingCount = getGiftRoomMappingCount(vo.getRoomId());
//        序号不能大于关联表情总数,并且排序号不能小于1
            if (newSort > giftRoomMappingCount || newSort < 1) {
                throw new BusinessException(BizErrorCode.COMP_GIFT_MAPPING_SORT_SET_ERROR);
            }
            GiftMapping giftMapping = giftMappingMapper.selectOne(Wrappers.<GiftMapping>lambdaQuery().eq(GiftMapping::getId, vo.getBindId()));
            Integer oldSort = giftMapping.getSort();
            if (!newSort.equals(oldSort)) {
//                更新房间下的其他关联礼品的排序
                if (newSort > oldSort) {
                    updateRegionGiftMappingSort(vo.getRoomId(), oldSort, newSort, ChangeSortRuleEnum.DECREMENT);
                } else {
                    updateRegionGiftMappingSort(vo.getRoomId(), newSort, oldSort, ChangeSortRuleEnum.INCREMENTAL);
                }
                giftMapping.setSort(newSort);
                giftMapping.setUpdatedAt(LocalDateTime.now());
                return updateById(giftMapping);
            }
        } finally {
            RedisLocker.unlock(lockKey, vo.getRoomId());
        }
        return true;
    }

    @Override
    public PageVO<GiftRoomStatDTO> statRoomDataList(GiftRoomStatVO vo) {
        Integer pageSize = vo.getPageSize();
        Integer pageIndex = vo.getPage();
        Integer startIndex = (pageIndex - 1) * pageSize;
        LocalDate startTime = LocalDate.parse(vo.getStartTime(), DATE_FORMATTER_YMD);
//        由于between是大于等于和小于,所以结束时间增加一天
        LocalDate endTime = LocalDate.parse(vo.getEndTime(), DATE_FORMATTER_YMD).plusDays(1);
        String roomId = vo.getRoomId();
        Integer total = giftRecordMapper.countStatGiftRoom(roomId, startTime, endTime);
        List<GiftRoomStatDTO> list = giftRecordMapper.statRoomDataList(roomId, startTime, endTime, startIndex, pageSize);
        return new PageVO<>(pageIndex, pageSize, total, list);
    }

    @Override
    public PageVO<GiftRoomStatDetailDTO> statRoomDetailDataList(GiftRoomStatDetailVO vo) {
        Integer pageSize = vo.getPageSize();
        Integer pageIndex = vo.getPage();

        LocalDate startTime = LocalDate.parse(vo.getStartTime(), DATE_FORMATTER_YMD);
//        由于between是大于等于和小于,所以结束时间增加一天
        LocalDate endTime = LocalDate.parse(vo.getEndTime(), DATE_FORMATTER_YMD).plusDays(1);
        String roomId = vo.getRoomId();
        Integer giftId = vo.getGiftId();
        LambdaQueryWrapper<GiftRecord> where = Wrappers.lambdaQuery();

        where.eq(GiftRecord::getRoomId, roomId)
                .eq(GiftRecord::getGiftId, giftId)
                .between(GiftRecord::getCreatedAt, startTime, endTime)
                .orderByDesc(GiftRecord::getCreatedAt);

        Page<GiftRecord> page = new Page<>(pageIndex, pageSize);
        Page<GiftRecord> quickRepliesEntityPage = giftRecordMapper.selectPage(page, where);
        List<GiftRoomStatDetailDTO> list = new ArrayList<>();
        if (Objects.nonNull(quickRepliesEntityPage) && quickRepliesEntityPage.getTotal() > 0) {
            list = quickRepliesEntityPage.getRecords().stream().map(entity -> {
                GiftRoomStatDetailDTO dto = new GiftRoomStatDetailDTO();
                BeanUtil.copyProperties(entity, dto, false);
                return dto;
            }).collect(Collectors.toList());
        }
        return new PageVO<>(pageIndex, pageSize, (int) page.getTotal(), list);
    }

    @Override
    public Boolean exportGift(GiftRecordExportVO vo) {
        RoomsEntityDTO roomInfo = roomInnerService.getRoomInfo(vo.getRoomId());
        if (roomInfo == null) {
            throw new BusinessException(BizErrorCode.EMPTY_ROOM);
        }
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime beginTime = now;
        LocalDateTime endTime = now;
        if (org.apache.commons.lang3.StringUtils.isNotBlank(vo.getStartTime())) {
            beginTime = DateUtil.parseLocalDateTime(vo.getStartTime(), DatePattern.NORM_DATE_PATTERN);
            beginTime = beginTime.truncatedTo(ChronoUnit.DAYS);
        }
        if (org.apache.commons.lang3.StringUtils.isNotBlank(vo.getEndTime())) {
            endTime = DateUtil.parseLocalDateTime(vo.getEndTime(), DatePattern.NORM_DATE_PATTERN);
            endTime = endTime.with(LocalTime.MAX);
        }
        Token token = TokenThreadLocal.getToken();
        Integer accountId = token.getAccountId();
        String fileName = "roomId_" + vo.getRoomId() + "_gift_record_list_" + DateUtil.format(now, DatePattern.PURE_DATETIME_PATTERN);
        ExportEntity ee = new ExportEntity();

        Map<String, Object> param = new HashMap<>();
        param.put("file_name", fileName);
        param.put("account_id", accountId);
        param.put("begin_time", DateUtil.formatLocalDateTime(beginTime));
        param.put("end_time", DateUtil.formatLocalDateTime(endTime));
        param.put("room_id", vo.getRoomId());
        param.put("sort_type", org.apache.commons.lang3.StringUtils.isBlank(vo.getSortType()) ? Constants.SORT_DESC : vo.getSortType());
        ee.setSourceId(vo.getRoomId());
        ee.setTitle(JSON.toJSONString(GIFT_RECORD_EXPORT_HEADER));
        ee.setParams(JSON.toJSONString(param));
        ee.setExt("csv");
        ee.setExport(GIFT_RECORD_SERVICE_EXPORT);
        ee.setRoomId(vo.getRoomId());
        ee.setIlId(roomInfo.getIlId());
        ee.setAccountId(accountId);
        ee.setFileName(fileName);
        ee.setCallback(GIFT_RECORD_SERVICE_GIFT_RECORD_EXPORT);
        return exportService.createExport(ee);
    }

    /**
     * 排序规则设置枚举类
     */
    private enum ChangeSortRuleEnum {
        /**
         * 递增排序
         */
        INCREMENTAL(1),
        /**
         * 递减排序
         */
        DECREMENT(-1);
        /**
         * 排序变动值
         */
        private final Integer changeSort;

        ChangeSortRuleEnum(Integer changeSort) {
            this.changeSort = changeSort;
        }
    }
}
